feat(py): generate middleware#5253
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements a robust middleware system for Genkit, enabling interception and modification of model generation, API calls, and tool executions. It introduces the genkit-plugin-middleware package, providing standard middleware for retries, fallbacks, tool approval, skills, and filesystem operations. Core generation logic was refactored to handle middleware normalization and per-call scoping. Feedback identifies redundant model copies in the asynchronous generation methods and a design conflict between validation tests and the normalization implementation. Furthermore, the reviewer noted blocking synchronous I/O in asynchronous tool implementations and issues with the jitter calculation in the retry middleware that could cause delays to exceed configured maximums.
Python middleware for
generate()—use=[...]Adds a middleware system for Python that lets you intercept and wrap
generate()calls at three granularities: the full generate iteration, each model API call, and each tool execution. Replaces the old ModelMiddleware abstraction.What it does
Middleware is applied per-
generate()call via ause=[...]parameter. Each entry in the list wraps the call in a chain — first entry is outermost (runs first/last). Three hooks are available:wrap_generate— wraps each iteration of the tool loop (model call + tool resolution). Runs once per agentic turn.wrap_model— wraps each raw model API call. Use for retry, fallback, logging latency.wrap_tool— wraps each individual tool execution. Use for approval gates, sandboxing, error enrichment.tools()— contribute extra tools dynamically pergenerate()call (e.g. skills libraries, sandboxed filesystem ops). Tools are scoped to the call and don't pollute the root registry.Defining and registering middleware inline (app developers)
Subclass
BaseMiddleware(a Pydantic model) and decorate it using the@ai.middlewaredecorator on yourGenkitinstance. This automatically registers the middleware with the registry so that it is discoverable by the Dev UI and usable across your app:Dev UI integration
Any middleware registered via plugins or decorated with @ai.middleware is automatically available in the Dev UI — no extra registration steps needed.
Once registered, the middleware shows up on the Model Runner page in the Dev UI, where you can mix-and-match middleware and set config values interactively. When you run a generate call from there, the Dev UI passes a MiddlewareRef — a name plus a config dict — into generate_action. The framework resolves that ref against the registry, instantiates the middleware class with the provided config (cls(**config)), and runs the chain exactly as it would inline.
Pre-packaging middleware through a plugin (plugin authors)
Use new_middleware to build a MiddlewareDesc from a BaseMiddleware subclass, then wrap them with middleware_plugin to produce a standard Plugin for plugins=[...]. Note that plugin middleware classes do not require decorators:
mylib/middleware.py (plugin author):